﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
//using Microsoft.VisualBasic;
using sdd.subsystems.display;
using sdd.subsystems.audio;
using sdd.utilities;
using sdd.apps;


// NISTON StreamOne - Streaming Audio Tuner App for SDD#
// This screen is the main UI for the Streaming Tuner
// (c) 2014-2015 Chris Burri. MIT License applies. 
// See http://niston.wordpress.com

namespace sdd.subsystems.ui.screens
{
    public class ScrTuner : IScreen
    {

    #region Private Members
        // display labels
        private int _ProgramID;
        private string _ArtistAndTitle = "";
        private string _StationName = "";
        private string _StreamFormat = "";
        private int _LevelLeft = 0;
        private int _LevelLeftPeak = 0;
        private DateTime _LevelLeftPeakUpdated;
        private int _LevelRight = 0;
        private int _LevelRightPeak = 0;
        private DateTime _LevelRightPeakUpdated;
        private int _LevelBuffer = 0;
        private int _LevelVolume = 0;
        private string _Clock = "";

        private readonly object updateLock = new object();

        // animation running flag
        private bool animationUpdating = false;

        // tuner op status
        private Tuner.OperationalStatuses _OperationalStatus;        
        
        // menu items
        public MenuItem mnuDisplay = new MenuItem("streamone.appmenu.display", "Display");      // display category in appmenu
        private OptionsMenuItem<int> optPeakHoldTime;                                           // peak hold options

        // ?
        apps.IApplication Application  { set; get; }

        private Tuner Tuner;

        // scrolling artist/title
        private bool _artistTitleScrollSlowMode = false;
        private bool _artistTitleScrollSlowToggle = false;
        private int _artistTitleScrollPosition = 1;
        private DateTime _artistTitleChanged = DateTime.Now;

        // blinker control
        private bool _blinkProgramIdToggle = false;
        private bool _blinkProgramIdEnable = false;

        // Function timers
        private System.Timers.Timer ScrollTimer = new System.Timers.Timer();
        private System.Timers.Timer BlinkProgramIdTimer = new System.Timers.Timer();
        private System.Timers.Timer UpdateUITimer = new System.Timers.Timer();

        private bool isUpdatingUI = false;
    #endregion
    
    #region Constructor/Destructor
        public ScrTuner(Tuner tunerApplication)
        {
            // reference to our owner (IApplication)
            Tuner = tunerApplication;

            // hooking timer events
            ScrollTimer.Elapsed += Animation_Elapsed;
            BlinkProgramIdTimer.Elapsed += BlinkProgramId_Elapsed;
            UpdateUITimer.Elapsed += UpdateUITimer_Elapsed;
            
            // create OptionsMenuItem and settings list for peak hold function
            List<OptionsMenuItem<int>.ListEntry<int>> peakHoldSettings = new List<OptionsMenuItem<int>.ListEntry<int>>();
            peakHoldSettings.Add(new OptionsMenuItem<int>.ListEntry<int>("Disabled", 0));
            peakHoldSettings.Add(new OptionsMenuItem<int>.ListEntry<int>("350ms", 350));
            peakHoldSettings.Add(new OptionsMenuItem<int>.ListEntry<int>("750ms", 750));
            peakHoldSettings.Add(new OptionsMenuItem<int>.ListEntry<int>("2000ms", 2000));
            peakHoldSettings.Add(new OptionsMenuItem<int>.ListEntry<int>("4000ms", 4000));
            peakHoldSettings.Add(new OptionsMenuItem<int>.ListEntry<int>("8000ms", 8000));
            optPeakHoldTime = new OptionsMenuItem<int>("streamone.screens.tuner.peakhold.time", "Peak Hold", peakHoldSettings, 3);

            // start dynamic UI updates
            UpdateStart();

        }

        ~ScrTuner()
        {
            // unhook timers
            ScrollTimer.Elapsed -= Animation_Elapsed;
            BlinkProgramIdTimer.Elapsed -= BlinkProgramId_Elapsed;
            UpdateUITimer.Elapsed -= UpdateUITimer_Elapsed;
            // stop dynamic UI updates
            UpdateStop();
        }
    #endregion

    #region Display Label Properties, Update Methods, Blinker Control
        public string Clock
        {
            set
            {
                if (value != _Clock)
                {
                    _Clock = value;
                    lock (Display.SyncLock)
                    {
                        Display.DrawFilledRectangle(0, 170, 42, 192, 52);
                        Display.SetCursorCoordinate(170, 42);
                        Display.SetCurrentFont(ScreenManager.FONT_PROP_SMALL);
                        Display.WriteText(_Clock);
                    }
                }
            }
            get
            {
                return _Clock;
            }
        }
        
        public int ProgramID
        {
            get { return _ProgramID; }
            set
            {
                if (value != _ProgramID)
                {
                    _ProgramID = value;
                    UpdateProgramId();
                }
            }
        }

        private void UpdateProgramId()
        {
            lock (Display.SyncLock) { Display.UpdateLabel(ScreenManager.LABEL_PROGRAM_ID, _ProgramID.ToString()); }
        }

        public bool BlinkProgramId
        {
            set
            {
                _blinkProgramIdEnable = value;
                if (!_blinkProgramIdEnable) { UpdateProgramId(); }
                    lock (Display.SyncLock) { Display.UpdateLabel(ScreenManager.LABEL_PROGRAM_ID, _ProgramID.ToString()); }
            }
            get
            {
                return _blinkProgramIdEnable;
            }
        }

        public int LevelLeft
        {
            get { return _LevelLeft; }
            set
            {
                if (value != _LevelLeft)
                {
                    _LevelLeft = value;
                    UpdateLevelLeft();
                }
            }
        }

        private void UpdateLevelLeft()
        {
            lock (Display.SyncLock)
            {
                // draw level bar
                int percent = (int)Math.Floor((float)(_LevelLeft) / 32767f * 100f);
                Display.DrawBar(51, 6, 4, 92, (byte)(percent));

                // peak hold enabled?
                if (optPeakHoldTime.SelectedEntryValue > 0)
                {
                    // new/same peak?
                    if (_LevelLeft >= _LevelLeftPeak)
                    {
                        // update
                        _LevelLeftPeak = _LevelLeft;
                        _LevelLeftPeakUpdated = DateTime.Now;
                    }
                    // current peak older than peak hold time?
                    if (DateTime.Now.Subtract(_LevelLeftPeakUpdated).TotalMilliseconds > optPeakHoldTime.SelectedEntryValue)
                    {
                        // reset to current level
                        _LevelLeftPeak = _LevelLeft;
                    }

                    // calculate new peak hold mark position
                    int percentHold = (int)Math.Floor((float)(_LevelLeftPeak) / 32767f * 100f);
                    byte peakPos = Display.MeasureBar(51, 6, 4, 92, (byte)(percentHold));

                    // draw peak hold mark
                    if (peakPos > 0)
                    {
                        Display.DrawLine(peakPos, 51, peakPos, 55);
                    }
                }
            }
        }

        public int LevelRight
        {
            get { return _LevelRight; }
            set
            {
                if (value != _LevelRight)
                {
                    _LevelRight = value;
                    UpdateLevelRight();
                }
            }
        }

        private void UpdateLevelRight()
        {
            lock (Display.SyncLock)
            {
                // draw level bar
                int percent = (int)Math.Floor((float)(_LevelRight) / 32767f * 100f);
                Display.DrawBar(59, 6, 4, 92, (byte)(percent));

                // peak hold enabled?
                if (optPeakHoldTime.SelectedEntryValue > 0)
                {
                    // new/same peak?
                    if (_LevelRight >= _LevelRightPeak)
                    {
                        // update
                        _LevelRightPeak = _LevelRight;
                        _LevelRightPeakUpdated = DateTime.Now;
                    }
                    // current peak older than peak hold time?
                    if (DateTime.Now.Subtract(_LevelRightPeakUpdated).TotalMilliseconds > optPeakHoldTime.SelectedEntryValue)
                    {
                        // reset
                        _LevelRightPeak = _LevelRight;
                    }

                    // calculate pixel position
                    int percentHold = (int)Math.Floor((float)(_LevelRightPeak) / 32767f * 100f);
                    byte peakPos = Display.MeasureBar(51, 6, 4, 92, (byte)(percentHold));


                    // draw peak hold mark
                    if (peakPos > 0)
                    {
                        Display.DrawLine(peakPos, 59, peakPos, 63);
                    }
                }
            }
        }

        private void ClearPeakHold()
        {
            lock (Display.SyncLock)
            {
                // R
                Display.DrawBar(59, 6, 4, 92, 0);
                // L
                Display.DrawBar(51, 6, 4, 92, 0);
            }
        }

        public int LevelBuffer
        {
            get { return _LevelBuffer; }
            set
            {
                if (value != _LevelBuffer)
                {
                    _LevelBuffer = value;
                    UpdateLevelBuffer();
                }
            }
        }
        
        private void UpdateLevelBuffer()
        {
            lock (Display.SyncLock) 
            { Display.DrawBar(59, 127, 4, 64, (byte)(_LevelBuffer)); }
        }

        public int LevelVolume
        {
            get { return _LevelVolume; }
            set
            {
                if (value != _LevelVolume)
                {
                    _LevelVolume = value;
                    UpdateLevelVolume();
                }
            }
        }

        public void UpdateLevelVolume()
        {
            lock(Display.SyncLock) { Display.DrawBar(51, 127, 4, 64, (byte)(_LevelVolume)); }
        }

        public string ArtistAndTitle
        {
            get { return _ArtistAndTitle; }
            set
            {
                if (value != _ArtistAndTitle)
                {
                    _ArtistAndTitle = value;
                    _artistTitleScrollPosition = 1;
                    _artistTitleChanged = DateTime.Now;
                    UpdateArtistAndTitle();
                }
            }
        }

        private void UpdateArtistAndTitle()
        {
            string croppedArtistAndTitle = "";
            if (ArtistAndTitle.Length > 27)
            {
                croppedArtistAndTitle = ArtistAndTitle.Substring(0, 27);
            }
            else
            {
                croppedArtistAndTitle = ArtistAndTitle;
            }
            lock (Display.SyncLock)
            {
                // clear area
                Display.DrawFilledRectangle(0, 0, 29, 192, 39);
                // set default font
                Display.SetCurrentFont(ScreenManager.FONT_MONO_DEFAULT);
                // cursor to 1,30
                Display.SetCursorCoordinate(0, 29);
                // write title & artist
                Display.WriteText(croppedArtistAndTitle);
            }

        }

        public string StationName
        {
            get
            {
                return _StationName;
            }
            set
            {
                if (value != _StationName)
                {
                    _StationName = value;

                    UpdateStationName();
                }
            }
        }

        private void UpdateStationName()
        {
            string croppedStationName;

            if (_StationName.Length > 20)
            {
                croppedStationName = _StationName.Substring(0, 20);
            }
            else
            {
                croppedStationName = _StationName;
            }

            lock (Display.SyncLock)
            {
                // clear area
                Display.DrawFilledRectangle(0, 0, 0, 161, 15);
                // cursor to 1,1
                Display.SetCursorCoordinate(0, 0);
                // set large font
                Display.SetCurrentFont(ScreenManager.FONT_PROP_LARGE);
                // write station name
                Display.WriteText(croppedStationName);
            }            
        }

        public string StreamFormat
        {
            get { return _StreamFormat; }
            set
            {
                if (value != _StreamFormat)
                {
                    _StreamFormat = value;
                    UpdateStreamFormat();
                }
            }
        }

        private void UpdateStreamFormat()
        {
            lock (Display.SyncLock)
            {
                // clear area
                Display.DrawFilledRectangle(0, 0, 16, 160, 26);
                // set small font
                Display.SetCurrentFont(ScreenManager.FONT_PROP_SMALL);
                // set cursor
                Display.SetCursorCoordinate(0, 16);
                // write format info
                Display.WriteText(StreamFormat);
            }

        }

        public Tuner.OperationalStatuses OperationalStatus
        {
            get { return _OperationalStatus; }
            set
            {
                if (value != _OperationalStatus)
                {
                    _OperationalStatus = value;
                    UpdateOperationalStatus();
                }
            }
        }

        private void UpdateOperationalStatus()
        {
            lock (Display.SyncLock)
            {
                Display.SetCursorCoordinate(127, 42);
                // using audio symbols font for operational status indicator
                Display.SetCurrentFont(ScreenManager.FONT_MONO_AUDIOSYMBOLS);
                switch (_OperationalStatus)
                {
                    case Tuner.OperationalStatuses.Stop:
                        Display.WriteText("B"); 
                        break;
                    case Tuner.OperationalStatuses.Play:
                        Display.WriteText("A");
                        break;
                    case Tuner.OperationalStatuses.Next:
                        Display.WriteText("N");
                        break;
                    case Tuner.OperationalStatuses.Previous:
                        Display.WriteText("O");
                        break;
                }
            }
        }


    #endregion

    #region IScreen Implementation
        
        public string ScreenName { get { return "Tuner"; } }
        public IDisplay Display { get; set; }
        public Director Director { get; set; }
        public ScreenManager Manager { get; set; }

        public void Initialize()
        {
            lock (Display.SyncLock)
            {
                // initialize our programid label on the display
                Display.InitializeLabel(ScreenManager.LABEL_PROGRAM_ID, 162, 1, 192, 22, JustificationVertical.Top, JustificationHorizontal.Right, ScreenManager.FONT_PROP_LCD, 0, 1);
            }

            // register menu items for options concerning this screen
            Manager.Menus.Register(mnuDisplay, Tuner.AppMenu); // category "Display"
            Manager.Menus.Register(optPeakHoldTime, mnuDisplay); // peak hold fall-off delay (ms)
            
            //Dictionary<string, string> Devices = new Dictionary<string, string>();
            //foreach(string device in Tuner.Streamer.Devices)
            //{
            //    Devices.Add(device, device);
            //}
            //OptionsMenuItem<string> slmiDevices = new OptionsMenuItem<string>("Output Device", Devices, Devices.First().Value);
            //Manager.Menus.Register(slmiDevices, Manager.Menus.Menu("Audio"));
        }

        public void Show()
        {
            InitializeScreen();
            RefreshScreen();
            UpdateStart();
        }

        public void Hide()
        {
            UpdateStop();
        }

        public void ProcessKeyPadEvent(KeypadEventArgs keyPadEventArgs)
        {
            switch (keyPadEventArgs.Key)
            {
                case KeypadKeys.Right:
                    // Next Station
                    if (IsDirectorReady())
                    {
                        Manager.Buzz();
                        Tuner.Next();
                    }
                    break;
                case KeypadKeys.Left:
                    // Previous Station
                    if (IsDirectorReady())
                    {
                        Manager.Buzz();
                        Tuner.Previous();
                    }
                    break;
                case KeypadKeys.Center:
                    // toggle PLAY/STOP
                    Manager.Buzz();
                    if (Tuner.Streamer.CurrentStream == null) { Tuner.Play(); } else { Tuner.Stop(); }
                    break;
                case KeypadKeys.App:
                    // show app menu
                    Manager.Buzz();
                    Manager.Show(Tuner.AppMenuScreen);
                    break;
            }
        }

    #endregion

    #region Private Methods

        private void UpdateStart()
        {
            ScrollTimer.Interval = 350;
            BlinkProgramIdTimer.Interval = 250;
            UpdateUITimer.Interval = 75;
            UpdateUITimer.Start();
            BlinkProgramIdTimer.Start();
            ScrollTimer.Start();
        }


        private void UpdateStop()
        {
            UpdateUITimer.Stop();
            BlinkProgramIdTimer.Stop();
            ScrollTimer.Stop();
        }

        private void UpdateUITimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (!isUpdatingUI)
            {
                lock (Display.SyncLock)
                {
                    isUpdatingUI = true;

                    // update operational status indicator
                    OperationalStatus = Tuner.OperationalState;

                    if (Tuner.Streamer.CurrentStream != null)
                    {
                        // show current stream's network buffer level
                        LevelBuffer = Tuner.Streamer.CurrentStream.BufferLevel;
                        // show output stream audio levels
                        LevelLeft = Tuner.Streamer.AudioLevels.Left;
                        LevelRight = Tuner.Streamer.AudioLevels.Right;
                    }
                    else
                    {
                        // reset levels display
                        LevelLeft = 0;
                        LevelRight = 0;
                        LevelBuffer = 0;
                        ClearPeakHold();
                    }   
                }
                
                isUpdatingUI = false;
            }

        }


        private void Animation_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (!animationUpdating)
            {
                lock (Display.SyncLock)
                {
                    animationUpdating = true;

                    // artist/title line scrolling - text too long to fit on one line?
                    if (_ArtistAndTitle.Length > 27)
                    {
                        // hold scrolling for first 2 seconds
                        if (DateTime.Now.Subtract(_artistTitleChanged) > TimeSpan.FromSeconds(1))
                        {
                            // yes, need scroll!
                            _artistTitleScrollSlowToggle = !_artistTitleScrollSlowToggle;
                            // normal mode or slow mode toggle cycle?
                            if (!_artistTitleScrollSlowMode | _artistTitleScrollSlowToggle)
                            {

                                // increase current start position
                                _artistTitleScrollPosition++;

                                // add spacers to the artist/title
                                string lineText = _ArtistAndTitle + "           ";

                                // take string from start position
                                string lineDisplay = lineText.Substring(_artistTitleScrollPosition);

                                // result is shorter than line lenght?
                                if (lineDisplay.Length < 27)
                                {
                                    // fill up 
                                    lineDisplay = lineDisplay + lineText.Substring(0, 27 - lineDisplay.Length);
                                }

                                // truncate to display width in chars (27 here)
                                if (lineDisplay.Length > 27)
                                {
                                    lineDisplay = lineDisplay.Left(27);
                                }

                                // end reached?
                                if (_artistTitleScrollPosition == lineText.Length)
                                {
                                    _artistTitleScrollPosition = 0;
                                }

                                //lock (updateUILock)
                                {
                                    Display.SetCurrentFont(ScreenManager.FONT_MONO_DEFAULT);
                                    Display.SetCursorCoordinate(0, 29);
                                    Display.WriteText(lineDisplay);
                                }
                            }
                        }
                    }

                    animationUpdating = false;
                }
            }
        }
        
        private void BlinkProgramId_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            //  program ID blinker
            _blinkProgramIdToggle = !_blinkProgramIdToggle;
            if (BlinkProgramId)
            {
                lock (Display.SyncLock)
                {
                    if (_blinkProgramIdToggle)
                    {
                        // hide
                        Display.UpdateLabel(ScreenManager.LABEL_PROGRAM_ID, "");
                    }
                    else
                    {
                        // show
                        Display.UpdateLabel(ScreenManager.LABEL_PROGRAM_ID, _ProgramID.ToString());
                    }
                }
            }

            
            
        }
    
        private void InitializeScreen()
        {
            lock (Display.SyncLock)
            {
                Display.ClearScreen();
                Display.SetAutoScroll(false);
                Display.SetCurrentFont(ScreenManager.FONT_PROP_SMALL);
                // level meters
                Display.DrawLine(6, 57, 98, 57);
                Display.SetCursorCoordinate(0, 51);
                Display.WriteText("L");
                Display.SetCursorCoordinate(0, 58);
                Display.WriteText("R");
                // volume meter
                Display.SetCursorCoordinate(110, 51);
                Display.WriteText("VOL");
                // buffer meter
                Display.DrawLine(127, 57, 190, 57);
                Display.SetCursorCoordinate(110, 58);
                Display.WriteText("BUF");
                
            }
        }

        public void Refresh()
        {
            RefreshScreen();
        }

        private void RefreshScreen()
        {
            UpdateProgramId();
            UpdateOperationalStatus();
            UpdateStationName();
            UpdateStreamFormat();
            UpdateArtistAndTitle();
            UpdateLevelLeft();
            UpdateLevelRight();
            UpdateLevelBuffer();
            UpdateLevelVolume();
        }

        private bool IsDirectorReady()
        {
            return (Tuner.OperationalState == Tuner.OperationalStatuses.Play | Tuner.OperationalState == Tuner.OperationalStatuses.Stop);
        }


        IApplication IScreen.Application
        {
            get { return (IApplication)Tuner; }
            set
            {
                Tuner = (Tuner)value;
            }
        }
    }

    #endregion

}
